-------------------------------------------------------------------------------
-- Title      : Commonly used VHDL functions
-- Project    : 
-------------------------------------------------------------------------------
-- File       : functions.vhd
-- Author     : Paul W
-- Company    : 
-- Created    : 2012-09-26
-- Last update: 2012-12-06
-- Platform   : 
-- Standard   : VHDL'93
-------------------------------------------------------------------------------
-- Description: Commonly used VHDL functions package
-------------------------------------------------------------------------------
-- Copyright (c) 2012 
-------------------------------------------------------------------------------
-- Revisions  :
-- Date        Version  Author  Description
-- 2012-09-26  1.0      paul	Created
-------------------------------------------------------------------------------

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

package functions is

  function xor_reduce (signal data_slv : std_logic_vector)
    return std_logic;

  function xnor_reduce (signal data_slv : std_logic_vector)
    return std_logic;

  function and_reduce (signal data_slv : std_logic_vector)
    return std_logic;

  function evenParity (signal data_slv : std_logic_vector)
    return std_logic;

  function oddParity (signal data_slv : std_logic_vector)
    return std_logic;

  function clearBit (signal data_slv, bitSel : std_logic_vector)
    return std_logic_vector;
  
  function oneHot (signal data_slv : std_logic_vector)
    return std_logic_vector;

  function log2_ceil (num : integer)
    return integer;

  function bcdLen (bin : std_logic_vector)
    return integer;
  
  function to_bcd (bin : std_logic_vector)
    return std_logic_vector;

end functions;

package body functions is

  -- Single bit result of xor on every bit
  function xor_reduce (
    signal data_slv : std_logic_vector)
    return std_logic is
    variable result : std_logic;
  begin  -- xor_reduce
    result := '0';
    for ii in data_slv'range loop
      result := result xor data_slv(ii);
    end loop;  -- ii
    return result;
  end xor_reduce;

  -- Single bit result of xnor on every bit
  function xnor_reduce (
    signal data_slv : std_logic_vector)
    return std_logic is
    variable result : std_logic;
  begin  -- function xnor_reduce
    result := not(xor_reduce(data_slv));
    return result;
  end function xnor_reduce;

  -- Single bit result of and on every bit
  function and_reduce (
    signal data_slv : std_logic_vector)
    return std_logic is
    variable result : std_logic;
  begin  -- and_reduce
    result := '0';
    for ii in data_slv'range loop
      result := result and data_slv(ii);
    end loop;  -- ii
    return result;
  end and_reduce;

  -- Calculate bit needed to complete even parity of input
  function evenParity (
    signal data_slv : std_logic_vector)
    return std_logic is
    variable result : std_logic;
  begin  -- function evenParity
    result := xor_reduce(data_slv);
    return result;
  end function evenParity;

  -- Calculate bit needed to complete odd parity of input
  function oddParity (
    signal data_slv : std_logic_vector)
    return std_logic is
    variable result : std_logic;
  begin  -- function oddParity
    result := xnor_reduce(data_slv);
    return result;
  end function oddParity;

  -- Clear a single bit 
  function clearBit (
    signal data_slv, bitSel : std_logic_vector)
    return std_logic_vector is
    variable result : std_logic_vector((data_slv'length)-1 downto 0);
  begin  -- function clearBit
    result := (others => '0');
    for ii in data_slv'range loop
      if ii = to_integer(unsigned(bitSel)) then
        -- only true in one case
        result(ii) := '0';
      else
        result(ii) := data_slv(ii);
      end if;
    end loop;  -- ii
    return result;
  end function clearBit;
  
  -- Return an slv with one bit set, specified by the input slv
  function oneHot (
    signal data_slv : std_logic_vector)
    return std_logic_vector is
    variable result : std_logic_vector((2**(data_slv'length))-1 downto 0);
  begin  -- function oneHot
    result := (others => '0');
    for ii in 0 to (2**(data_slv'length))-1 loop
      if ii = to_integer(unsigned(data_slv)) then
        -- only true in one case
        result(ii) := '1';
      end if;
    end loop;  -- ii
    return result;
  end function oneHot;

  -- log 2, ceiling
  function log2_ceil (
    num : integer)
    return integer is
    variable tmp : integer;
    variable result : integer;
  begin

    assert num > 0 report "log2_ceil(0) is not valid"
      severity failure;
    
    tmp    := 2;
    result := 1;
    
    while tmp < num loop
      tmp    := tmp*2;
      result := result + 1;
    end loop;

    return result;
  end function log2_ceil;

  -- Determine the length of bits needed for BCD representation
  function bcdLen (bin : std_logic_vector) return integer is
    variable threeCnt : natural range 0 to 2 := 0;
    variable bitCnt   : integer              := 0;
  begin
    threeCnt := 0;
    bitCnt   := 0;
    
    -- Count through the given slv by 3s. This yields the returned number of bits.
    for ii in 0 to bin'length-1 loop
      if threeCnt = 2 then
        threeCnt := 0;
        bitCnt   := bitCnt + 1;
      else
        threeCnt := threeCnt + 1;
      end if;
    end loop;  -- ii

    -- We need one more decimal place if threeCnt did not just roll
    if threeCnt /= 0 then
      bitCnt := bitCnt + 1; 
    end if;

    bitCnt := bitCnt * 4;
    return bitCnt;

  end bcdLen;

  -- Return the BCD representation of a std_logic_vector. Double Dabble Style
  function to_bcd (bin : std_logic_vector) return std_logic_vector is
    variable bcdLength : integer                                  := bcdLen(bin);
    variable bcd       : std_logic_vector(bcdLen(bin)-1 downto 0) := (others => '0');
    variable bint      : std_logic_vector(bin'length-1 downto 0)  := bin;

  begin
    bcd  := (others => '0');
    bint := bin;
    
    for ii in 0 to bin'length-1 loop    -- repeating n times for n input bits
      bcd(bcdLength-1 downto 1)   := bcd(bcdLength-2 downto 0);  --shifting the bits.
      bcd(0)                      := bint(bin'length-1);
      bint(bin'length-1 downto 1) := bint(bin'length-2 downto 0);
      bint(0)                     := '0';

      for jj in 0 to (bcdLength/4)-1 loop
        if ii < bin'length-1 and unsigned(bcd(jj*4+3 downto jj*4)) > "0100" then  --add 3 if BCD digit is greater than 4.
          bcd(jj*4+3 downto jj*4) := std_logic_vector(
            unsigned(bcd(jj*4+3 downto jj*4)) + "0011");
        end if;
      end loop;  -- jj
      
    end loop;  -- ii
    
    return bcd;
    
  end to_bcd;
  
end functions;
